Creating new pipeline using seurat v4.0.2 available 2021.06.08

Load libraries required for Seuratv4

knitr::opts_knit$set(root.dir = "~/Desktop/10XGenomicsData/msAggr_scRNASeq/")
library(dplyr)
library(Seurat)
library(patchwork)
library(ggplot2)
library(clustree)

store session info

sink("msAggr_seurat-v1.20210608")
sessionInfo()
sink()

A note about using SCTransform versus ScaleData

https://bioconductor.org/packages/3.10/workflows/vignettes/simpleSingleCell/inst/doc/batch.html#62_for_gene-based_analyses >You can also normalize and scale data for the RNA assay. There are numerous resources on this, but Aaron Lun describes why the original log-normalized values should be used for DE and visualizations of expression quite well here: > >For gene-based procedures like differential expression (DE) analyses or gene network construction, it is desirable to use the original log-expression values or counts. The corrected values are only used to obtain cell-level results such as clusters or trajectories. Batch effects are handled explicitly using blocking terms or via a meta-analysis across batches. We do not use the corrected values directly in gene-based analyses, for various reasons: > >It is usually inappropriate to perform DE analyses on batch-corrected values, due to the failure to model the uncertainty of the correction. This usually results in loss of type I error control, i.e., more false positives than expected. > >The correction does not preserve the mean-variance relationship. Applications of common DE methods like edgeR or limma are unlikely to be valid. > >Batch correction may (correctly) remove biological differences between batches in the course of mapping all cells onto a common coordinate system. Returning to the uncorrected expression values provides an opportunity for detecting such differences if they are of interest. Conversely, if the batch correction made a mistake, the use of the uncorrected expression values provides an important sanity check. > >In addition, the normalized values in SCT and integrated assays don’t necessary correspond to per-gene expression values anyway, rather containing residuals (in the case of the scale.data slot for each).

Mess with how to load 4 cell populations into single seurat object

SET SEED?????!!!!!

Set global variables

projectName <- "msAggr"
jackstraw.dim <- 40
projectName <- "msAggr"
jackstraw.dim <- 40
source("msAggr_AnalysisCode/read_10XGenomics_data.R")
setwd("../cellRanger/")
Warning: The working directory was changed to /Users/heustonef/Desktop/10XGenomicsData/cellRanger inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
data_file.list <- read_10XGenomics_data(sample.list = c("LSKm2", "CMPm2", "MEPm", "GMPm"))
seurat.object.data<-Read10X(data_file.list)
seurat.object<- create_percentMito_column(seurat.object)
Error in create_percentMito_column(seurat.object) : 
  could not find function "create_percentMito_column"

Clean up to free memory

remove(seurat.object.data)

Add mitochondrial metadata and plot some basic features

seurat.object[["percent.mt"]] <- PercentageFeatureSet(seurat.object, pattern = "^mt-")
VlnPlot(seurat.object, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3, pt.size = 0, fill.by = 'orig.ident')

plot1 <- FeatureScatter(seurat.object, feature1 = "nCount_RNA", feature2 = "percent.mt", group.by = "orig.ident", pt.size = 0.01)
plot2 <- FeatureScatter(seurat.object, feature1 = "nCount_RNA", feature2 = "nFeature_RNA", group.by = "orig.ident", pt.size = 0.01)
plot1 + plot2

remove low quality cells require: nFeature_RNA between 200 and 4000 (inclusive) –require: percent.mt <5–???

print(paste("original object:", nrow(seurat.object@meta.data), "cells", sep = " "))
[1] "original object: 41950 cells"
seurat.object <- subset(seurat.object, 
                                                subset = nFeature_RNA >=200 & 
                                                    nFeature_RNA <= 4000
                                                )
print(paste("new object:", nrow(seurat.object@meta.data), "cells", sep = " "))
[1] "new object: 40815 cells"

Normalization

Struggling to wrap my head around this one. It seems that SCTransform is best for batch correction, but NormalizeData and ScaleData are best for DGE. Several vignettes have performed both

`selection.method
How to choose top variable features. Choose one of :

vst: First, fits a line to the relationship of log(variance) and log(mean) using local polynomial regression (loess). Then standardizes the feature values using the observed mean and expected variance (given by the fitted line). Feature variance is then calculated on the standardized values after clipping to a maximum (see clip.max parameter).

mean.var.plot (mvp): First, uses a function to calculate average expression (mean.function) and dispersion (dispersion.function) for each feature. Next, divides features into num.bin (deafult 20) bins based on their average expression, and calculates z-scores for dispersion within each bin. The purpose of this is to identify variable features while controlling for the strong relationship between variability and average expression.

dispersion (disp): selects the genes with the highest dispersion values`

seurat.object <- NormalizeData(seurat.object, normalization.method = "LogNormalize", scale.factor = 10000)
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
seurat.object <- FindVariableFeatures(seurat.object, selection.method = "vst", nfeatures = 2000)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

Find variable features

seurat.object <- FindVariableFeatures(seurat.object, selection.method = "vst", nfeatures = 2000)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
top10 <- head(VariableFeatures(seurat.object), 10)
plot1 <- VariableFeaturePlot(seurat.object)
plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)
When using repel, set xnudge and ynudge to 0 for optimal results
plot1 + plot2

Scale data (linear transformation)

all.genes <- rownames(seurat.object)
seurat.object <- ScaleData(seurat.object, features = all.genes)
Centering and scaling data matrix

  |                                                                                                                           
  |                                                                                                                     |   0%
  |                                                                                                                           
  |======                                                                                                               |   5%
  |                                                                                                                           
  |============                                                                                                         |  11%
  |                                                                                                                           
  |==================                                                                                                   |  16%
  |                                                                                                                           
  |=========================                                                                                            |  21%

Save progress

save.image(file = paste0(projectName, '.RData'))

PCA

linear dimensional reduction. Default are based on VariableFeatures, but can be changed

seurat.object <- RunPCA(seurat.object, features = VariableFeatures(object = seurat.object))
VizDimLoadings(seurat.object, dims = 1:6, nfeatures = 10, reduction = "pca", ncol = 2)

DimPlot colored by orig.ident

Let’s put in a concerted effort to pick the right dimensionality using the newest software

save.image(paste0(projectName, ".RData"))
Error in paste0(projectName, ".RData") : object 'projectName' not found

Draw dim.reduction plots

JackStrawPlot(seurat.object, dims = 25:36)
Warning: Removed 18381 rows containing missing values (geom_point).

ElbowPlot(seurat.object, ndims = 50)
percent.variance(seurat.object@reductions$pca@stdev)

Number of PCs describing X% of variance

ElbowPlot(seurat.object, ndims = 50)

percent.variance(seurat.object@reductions$pca@stdev)

KNN dimensional reduction

Dim36

36 PCs describe 95% percent of variance, but one of the earlier critiques was that too many dimensions were included. Best way to deal with this is probably to try a couple of different ones. Let’s start with 36 because I dare to rebel.

tot.var <- percent.variance(seurat.object@reductions$pca@stdev, plot.var = FALSE, return.val = TRUE)
paste0("Num pcs for 80% variance:", length(which(cumsum(tot.var) <= 80)))
[1] "Num pcs for 80% variance:11"
paste0("Num pcs for 85% variance:", length(which(cumsum(tot.var) <= 85)))
[1] "Num pcs for 85% variance:16"
paste0("Num pcs for 90% variance:", length(which(cumsum(tot.var) <= 90)))
[1] "Num pcs for 90% variance:24"
paste0("Num pcs for 95% variance:", length(which(cumsum(tot.var) <= 95)))
[1] "Num pcs for 95% variance:36"

Generate UMAP data

seurat.object <- FindNeighbors(seurat.object, dims = 1:36)
Computing nearest neighbor graph
Computing SNN
seurat.object <- FindClusters(seurat.object, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9063
Number of communities: 15
Elapsed time: 9 seconds

Plot the results

seurat.object <- RunUMAP(seurat.object, dims = 1:36)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
10:13:32 UMAP embedding parameters a = 0.9922 b = 1.112
10:13:33 Read 40815 rows and found 36 numeric columns
10:13:33 Using Annoy for neighbor search, n_neighbors = 30
10:13:33 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:13:36 Writing NN index file to temp file /var/folders/4f/fwrj6fnn1dn4g8wsf0zv563hjsvl24/T//RtmppBwCob/filed75535a2180
10:13:36 Searching Annoy index using 1 thread, search_k = 3000
10:13:46 Annoy recall = 100%
10:13:48 Commencing smooth kNN distance calibration using 1 thread
10:13:50 Initializing from normalized Laplacian + noise
10:13:52 Commencing optimization for 200 epochs, with 1790844 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
10:14:11 Optimization finished
DimPlot(seurat.object, 
                reduction = "umap"
                ) + ggtitle("msAggr dim36 res0.5")

DimPlot(seurat.object,
                reduction = "umap", 
                group.by = "orig.ident"
                ) + ggtitle("msAggt dim36 orig.ident")

Dim24

36 dimensions looks very “swoopy”. might have asked for too many dimensions. Will try dim = 24, which accounts for 90% of variance

saveRDS(seurat.object, file = "msAggr_dim36.rds")

Plot the results

seurat.object <- FindNeighbors(seurat.object, dims = 1:24)
Computing nearest neighbor graph
Computing SNN
seurat.object <- FindClusters(seurat.object, resolution = 0.5)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1436767

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9066
Number of communities: 14
Elapsed time: 10 seconds
seurat.object <- RunUMAP(seurat.object, dims = 1:24)
11:02:14 UMAP embedding parameters a = 0.9922 b = 1.112
11:02:14 Read 40815 rows and found 24 numeric columns
11:02:14 Using Annoy for neighbor search, n_neighbors = 30
11:02:14 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:02:18 Writing NN index file to temp file /var/folders/4f/fwrj6fnn1dn4g8wsf0zv563hjsvl24/T//RtmppBwCob/filed75730bd234
11:02:18 Searching Annoy index using 1 thread, search_k = 3000
11:02:28 Annoy recall = 100%
11:02:29 Commencing smooth kNN distance calibration using 1 thread
11:02:31 Initializing from normalized Laplacian + noise
11:02:33 Commencing optimization for 200 epochs, with 1731126 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
11:02:52 Optimization finished
DimPlot(seurat.object, 
                reduction = "umap"
                ) + ggtitle("msAggr dim24 res0.5")

DimPlot(seurat.object,
                reduction = "umap", 
                group.by = "orig.ident"
                ) + ggtitle("msAggt dim24 orig.ident")

KNN conclusion

24 vs 36 dimensions doesn’t seem to make that much difference RE overall relationships between cells. Should probbaly create two separate objects and try a few plotting methods, but will start by focusing on 36 dimensions.

Cluster analysis

Will proceed with dim = 36 and do clustering analysis with a range of clusters. Later can do the tree-based over-clustering assessment

seurat.object <- readRDS("msAggr_dim36.rds")

Generate cluster profiles

Picking range of resolutions

for(x in c(0.5, 1, 1.5, 2, 2.5)){
    seurat.object <- FindClusters(seurat.object, resolution = x)
}
seurat.object <- RunUMAP(seurat.object, dims = 1:36)

Plot the clustering over different resolutions. Going to need a much better color palette, or try mixing in some different symbols

for(x in c(0.5, 1, 1.5, 2, 2.5)){
    seurat.object <- FindClusters(seurat.object, resolution = x)
}
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9063
Number of communities: 15
Elapsed time: 8 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8716
Number of communities: 23
Elapsed time: 8 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8463
Number of communities: 27
Elapsed time: 7 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8267
Number of communities: 36
Elapsed time: 7 seconds
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 40815
Number of edges: 1546414

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8097
Number of communities: 42
Elapsed time: 8 seconds
seurat.object <- RunUMAP(seurat.object, dims = 1:36)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
15:19:06 UMAP embedding parameters a = 0.9922 b = 1.112
15:19:06 Read 40815 rows and found 36 numeric columns
15:19:06 Using Annoy for neighbor search, n_neighbors = 30
15:19:06 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:19:10 Writing NN index file to temp file /var/folders/4f/fwrj6fnn1dn4g8wsf0zv563hjsvl24/T//RtmpebgnbZ/fileb3657e9aac
15:19:10 Searching Annoy index using 1 thread, search_k = 3000
15:19:19 Annoy recall = 100%
15:19:20 Commencing smooth kNN distance calibration using 1 thread
15:19:21 Initializing from normalized Laplacian + noise
15:19:24 Commencing optimization for 200 epochs, with 1790844 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
15:19:43 Optimization finished
for (meta.col in colnames(seurat.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        myplot <- DimPlot(seurat.object, 
                                            group.by = meta.col,
                                            reduction = "umap", 
                                            cols = colorRamps::primary.colors(n = length(levels(seurat.object@meta.data[[meta.col]])))
                                            ) + 
            ggtitle(paste0("msAggr dim36 res", gsub("RNA_snn_res", "", meta.col) ))
        plot(myplot)
    }
}

Evaluate cluster stability

Must ensure we have the right cluster stability, that is, cells that start in the same cluster tend to stay in the same cluster. If your data is over-clustered, cells will bounce between groups.

Following [this tutorial by Matt O.].https://towardsdatascience.com/10-tips-for-choosing-the-optimal-number-of-clusters-277e93d72d92.

Clustree

Previously my favourite has been Clustree, which gives a nice visual NB: For some reason clustree::clustree() didn’t work, whereas library(clustree) followed by clustree() did.

saveRDS(seurat.object, file = "msAggr_dim36.rds")
clustree(seurat.object, prefix = "RNA_snn_res.", node_colour = "sc3_stability") + 
    scale_color_continuous(low = 'red3', high = 'white')

clustree(seurat.object, prefix = "RNA_snn_res.", expres = 'data', node_colour = "sc3_stability") + 
    scale_color_continuous(low = 'red3', high = 'white')

clustree(seurat.object, prefix = "RNA_snn_res.", expres = 'scale.data', node_colour = "sc3_stability") + 
    scale_color_continuous(low = 'red3', high = 'white')

These data suggest that node stability is aweful! Need to figure out if this is a dimensional reduction error or a clustering error.

Why are clusters so unstable?

Differences could include: * cells in each population (cellranger v6 includes more cells than cellranger v1, especially in MEP) * dimensionality is incorrect * ScaleData didnt account for regression factors (e.g., “nCounts_RNA” or “nFeatures_RNA”) * Did not consider cell cycle * Incorrect normalization/scaling method * Clustering is too strict or not strict enough * neighborhood analysis used wrong parameters * Should include mitoC filter (there’s a chunk of MEP w/ mitoC @ ~40%) * SCTransform accounts better for sources of variability

clustree(seurat.object, prefix = "RNA_snn_res.", expres = 'counts', node_colour = "sc3_stability") + 
    scale_color_continuous(low = 'red3', high = 'white')

sapply(c("LSKm2", "CMPm2", "MEPm", "GMPm"), function(x) (c(nrow(seurat.object@meta.data[seurat.object@meta.data$orig.ident == x,]))))
LSKm2 CMPm2  MEPm  GMPm 
11004 12342  8791  8678 

Looks like MEPm is the only sample with that huge MitoC % lump @ 40%. What do these cells look like, otherwise?

for (x in c("LSKm2", "CMPm2", "MEPm", "GMPm")){
    h = hist(seurat.object@meta.data[seurat.object@meta.data$orig.ident == x, 'percent.mt'], breaks = 30, plot = FALSE)
    h$density = h$counts/sum(h$counts)*100
    plot(h,freq=FALSE, main =  paste(x, "percent mitoC"), xlab = "percent mitoC", ylab = "Frequency")
}

Save dim36 as is and try clustering analysis @ dim24

VlnPlot(subset(seurat.object, subset = orig.ident == "MEPm"), 
                features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 1, pt.size = 0, fill.by = 'ident', flip = TRUE)

Repeat clustering with dim24

One possibility is that I’ve included too many dimensions. Will see if 90% increases stability.

saveRDS(seurat.object, file = "msAggr_AnalysisCode/msAggr_dim36.rds")

Save object

saveRDS(seurat.object, file = "msAggr_dim24.rds")
saveRDS(seurat.object, file = "msAggr_dim24.rds")

Evaluate cluster stability

Must ensure we have the right cluster stability, that is, cells that start in the same cluster tend to stay in the same cluster. If your data is over-clustered, cells will bounce between groups.

Following [this tutorial by Matt O.].https://towardsdatascience.com/10-tips-for-choosing-the-optimal-number-of-clusters-277e93d72d92.

Clustree

Previously my favourite has been Clustree, which gives a nice visual NB: For some reason clustree::clustree() didn’t work, whereas library(clustree) followed by clustree() did.

for (meta.col in colnames(seurat.object@meta.data)){
    if(grepl(pattern = ("RNA_snn_res"), x = meta.col)==TRUE){
        myplot <- DimPlot(seurat.object, 
                                            group.by = meta.col,
                                            reduction = "umap", 
                                            cols = colorRamps::primary.colors(n = length(levels(seurat.object@meta.data[[meta.col]])))
                                            ) + 
            ggtitle(paste0("msAggr dim36 res", gsub("RNA_snn_res", "", meta.col) ))
        plot(myplot)
    }
}

Think I’ll explore regression factors using SCTransform in new document.

LS0tCnRpdGxlOiAibXNBZ2dyX3NldXJhdC12MSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKQ3JlYXRpbmcgbmV3IHBpcGVsaW5lIHVzaW5nIHNldXJhdCB2NC4wLjIgYXZhaWxhYmxlIDIwMjEuMDYuMDgKCkxvYWQgbGlicmFyaWVzIHJlcXVpcmVkIGZvciBTZXVyYXR2NAoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIn4vRGVza3RvcC8xMFhHZW5vbWljc0RhdGEvbXNBZ2dyX3NjUk5BU2VxLyIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNsdXN0cmVlKQpgYGAKc3RvcmUgc2Vzc2lvbiBpbmZvCmBgYHtyfQpzaW5rKCJtc0FnZ3Jfc2V1cmF0LXYxLjIwMjEwNjA4IikKc2Vzc2lvbkluZm8oKQpzaW5rKCkKYGBgCgojIEEgbm90ZSBhYm91dCB1c2luZyBTQ1RyYW5zZm9ybSB2ZXJzdXMgYFNjYWxlRGF0YWAKaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzLzMuMTAvd29ya2Zsb3dzL3ZpZ25ldHRlcy9zaW1wbGVTaW5nbGVDZWxsL2luc3QvZG9jL2JhdGNoLmh0bWwjNjJfZm9yX2dlbmUtYmFzZWRfYW5hbHlzZXMKPllvdSBjYW4gYWxzbyBub3JtYWxpemUgYW5kIHNjYWxlIGRhdGEgZm9yIHRoZSBSTkEgYXNzYXkuIFRoZXJlIGFyZSBudW1lcm91cyByZXNvdXJjZXMgb24gdGhpcywgYnV0IEFhcm9uIEx1biBkZXNjcmliZXMgd2h5IHRoZSBvcmlnaW5hbCBsb2ctbm9ybWFsaXplZCB2YWx1ZXMgc2hvdWxkIGJlIHVzZWQgZm9yIERFIGFuZCB2aXN1YWxpemF0aW9ucyBvZiBleHByZXNzaW9uIHF1aXRlIHdlbGwgaGVyZToKPgo+Rm9yIGdlbmUtYmFzZWQgcHJvY2VkdXJlcyBsaWtlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIChERSkgYW5hbHlzZXMgb3IgZ2VuZSBuZXR3b3JrIGNvbnN0cnVjdGlvbiwgaXQgaXMgZGVzaXJhYmxlIHRvIHVzZSB0aGUgb3JpZ2luYWwgbG9nLWV4cHJlc3Npb24gdmFsdWVzIG9yIGNvdW50cy4gVGhlIGNvcnJlY3RlZCB2YWx1ZXMgYXJlIG9ubHkgdXNlZCB0byBvYnRhaW4gY2VsbC1sZXZlbCByZXN1bHRzIHN1Y2ggYXMgY2x1c3RlcnMgb3IgdHJhamVjdG9yaWVzLiBCYXRjaCBlZmZlY3RzIGFyZSBoYW5kbGVkIGV4cGxpY2l0bHkgdXNpbmcgYmxvY2tpbmcgdGVybXMgb3IgdmlhIGEgbWV0YS1hbmFseXNpcyBhY3Jvc3MgYmF0Y2hlcy4gV2UgZG8gbm90IHVzZSB0aGUgY29ycmVjdGVkIHZhbHVlcyBkaXJlY3RseSBpbiBnZW5lLWJhc2VkIGFuYWx5c2VzLCBmb3IgdmFyaW91cyByZWFzb25zOgo+Cj5JdCBpcyB1c3VhbGx5IGluYXBwcm9wcmlhdGUgdG8gcGVyZm9ybSBERSBhbmFseXNlcyBvbiBiYXRjaC1jb3JyZWN0ZWQgdmFsdWVzLCBkdWUgdG8gdGhlIGZhaWx1cmUgdG8gbW9kZWwgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBjb3JyZWN0aW9uLiBUaGlzIHVzdWFsbHkgcmVzdWx0cyBpbiBsb3NzIG9mIHR5cGUgSSBlcnJvciBjb250cm9sLCBpLmUuLCBtb3JlIGZhbHNlIHBvc2l0aXZlcyB0aGFuIGV4cGVjdGVkLgo+Cj5UaGUgY29ycmVjdGlvbiBkb2VzIG5vdCBwcmVzZXJ2ZSB0aGUgbWVhbi12YXJpYW5jZSByZWxhdGlvbnNoaXAuIEFwcGxpY2F0aW9ucyBvZiBjb21tb24gREUgbWV0aG9kcyBsaWtlIGVkZ2VSIG9yIGxpbW1hIGFyZSB1bmxpa2VseSB0byBiZSB2YWxpZC4KPgo+QmF0Y2ggY29ycmVjdGlvbiBtYXkgKGNvcnJlY3RseSkgcmVtb3ZlIGJpb2xvZ2ljYWwgZGlmZmVyZW5jZXMgYmV0d2VlbiBiYXRjaGVzIGluIHRoZSBjb3Vyc2Ugb2YgbWFwcGluZyBhbGwgY2VsbHMgb250byBhIGNvbW1vbiBjb29yZGluYXRlIHN5c3RlbS4gUmV0dXJuaW5nIHRvIHRoZSB1bmNvcnJlY3RlZCBleHByZXNzaW9uIHZhbHVlcyBwcm92aWRlcyBhbiBvcHBvcnR1bml0eSBmb3IgZGV0ZWN0aW5nIHN1Y2ggZGlmZmVyZW5jZXMgaWYgdGhleSBhcmUgb2YgaW50ZXJlc3QuIENvbnZlcnNlbHksIGlmIHRoZSBiYXRjaCBjb3JyZWN0aW9uIG1hZGUgYSBtaXN0YWtlLCB0aGUgdXNlIG9mIHRoZSB1bmNvcnJlY3RlZCBleHByZXNzaW9uIHZhbHVlcyBwcm92aWRlcyBhbiBpbXBvcnRhbnQgc2FuaXR5IGNoZWNrLgo+Cj5JbiBhZGRpdGlvbiwgdGhlIG5vcm1hbGl6ZWQgdmFsdWVzIGluIFNDVCBhbmQgaW50ZWdyYXRlZCBhc3NheXMgZG9uJ3QgbmVjZXNzYXJ5IGNvcnJlc3BvbmQgdG8gcGVyLWdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgYW55d2F5LCByYXRoZXIgY29udGFpbmluZyByZXNpZHVhbHMgKGluIHRoZSBjYXNlIG9mIHRoZSBzY2FsZS5kYXRhIHNsb3QgZm9yIGVhY2gpLgoKCgpNZXNzIHdpdGggaG93IHRvIGxvYWQgNCBjZWxsIHBvcHVsYXRpb25zIGludG8gc2luZ2xlIHNldXJhdCBvYmplY3QKCgoKU0VUIFNFRUQ/Pz8/PyEhISEhCgojIyBTZXQgZ2xvYmFsIHZhcmlhYmxlcwoKYGBge3J9CnByb2plY3ROYW1lIDwtICJtc0FnZ3IiCmphY2tzdHJhdy5kaW0gPC0gNDAKYGBgCgoKCmBgYHtyfQpzb3VyY2UoIm1zQWdncl9BbmFseXNpc0NvZGUvcmVhZF8xMFhHZW5vbWljc19kYXRhLlIiKQoKYGBgCgoKYGBge3J9CnNldHdkKCIuLi9jZWxsUmFuZ2VyLyIpICMgdGVtcG9yYXJpbHkgY2hhbmdpbmcgd2Qgb25seSB3b3JrcyBpZiB5b3UgcnVuIHRoZSBlbnRpcmUgY2h1bmsgYXQgb25jZQpkYXRhX2ZpbGUubGlzdCA8LSByZWFkXzEwWEdlbm9taWNzX2RhdGEoc2FtcGxlLmxpc3QgPSBjKCJMU0ttMiIsICJDTVBtMiIsICJNRVBtIiwgIkdNUG0iKSkKc2V1cmF0Lm9iamVjdC5kYXRhPC1SZWFkMTBYKGRhdGFfZmlsZS5saXN0KQpgYGAKCgoKYGBge3J9CnNldXJhdC5vYmplY3Q8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gc2V1cmF0Lm9iamVjdC5kYXRhLCBtaW4uY2VsbHMgPSAzLCBtaW4uZ2VuZXMgPSAyMDAsIHByb2plY3QgPSBwcm9qZWN0TmFtZSkKYGBgCgpDbGVhbiB1cCB0byBmcmVlIG1lbW9yeQoKYGBge3J9CnJlbW92ZShzZXVyYXQub2JqZWN0LmRhdGEpCmBgYAoKCkFkZCBtaXRvY2hvbmRyaWFsIG1ldGFkYXRhIGFuZCBwbG90IHNvbWUgYmFzaWMgZmVhdHVyZXMKYGBge3J9CnNldXJhdC5vYmplY3RbWyJwZXJjZW50Lm10Il1dIDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KHNldXJhdC5vYmplY3QsIHBhdHRlcm4gPSAiXm10LSIpClZsblBsb3Qoc2V1cmF0Lm9iamVjdCwgZmVhdHVyZXMgPSBjKCJuRmVhdHVyZV9STkEiLCAibkNvdW50X1JOQSIsICJwZXJjZW50Lm10IiksIG5jb2wgPSAzLCBwdC5zaXplID0gMCwgZmlsbC5ieSA9ICdvcmlnLmlkZW50JywgKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD0yfQpwbG90MSA8LSBGZWF0dXJlU2NhdHRlcihzZXVyYXQub2JqZWN0LCBmZWF0dXJlMSA9ICJuQ291bnRfUk5BIiwgZmVhdHVyZTIgPSAicGVyY2VudC5tdCIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCBwdC5zaXplID0gMC4wMSkKcGxvdDIgPC0gRmVhdHVyZVNjYXR0ZXIoc2V1cmF0Lm9iamVjdCwgZmVhdHVyZTEgPSAibkNvdW50X1JOQSIsIGZlYXR1cmUyID0gIm5GZWF0dXJlX1JOQSIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiLCBwdC5zaXplID0gMC4wMSkKcGxvdDEgKyBwbG90MgpgYGAKCgpyZW1vdmUgbG93IHF1YWxpdHkgY2VsbHMKcmVxdWlyZTogbkZlYXR1cmVfUk5BIGJldHdlZW4gMjAwIGFuZCA0MDAwIChpbmNsdXNpdmUpCi0tcmVxdWlyZTogcGVyY2VudC5tdCA8NS0tPz8/CgpgYGB7cn0KcHJpbnQocGFzdGUoIm9yaWdpbmFsIG9iamVjdDoiLCBucm93KHNldXJhdC5vYmplY3RAbWV0YS5kYXRhKSwgImNlbGxzIiwgc2VwID0gIiAiKSkKc2V1cmF0Lm9iamVjdCA8LSBzdWJzZXQoc2V1cmF0Lm9iamVjdCwgCgkJCQkJCQkJCQkJCXN1YnNldCA9IG5GZWF0dXJlX1JOQSA+PTIwMCAmIAoJCQkJCQkJCQkJCQkJbkZlYXR1cmVfUk5BIDw9IDQwMDAKCQkJCQkJCQkJCQkJKQpwcmludChwYXN0ZSgibmV3IG9iamVjdDoiLCBucm93KHNldXJhdC5vYmplY3RAbWV0YS5kYXRhKSwgImNlbGxzIiwgc2VwID0gIiAiKSkKYGBgCgoKCiMjIE5vcm1hbGl6YXRpb24KClN0cnVnZ2xpbmcgdG8gd3JhcCBteSBoZWFkIGFyb3VuZCB0aGlzIG9uZS4gSXQgc2VlbXMgdGhhdCBTQ1RyYW5zZm9ybSBpcyBiZXN0IGZvciBiYXRjaCBjb3JyZWN0aW9uLCBidXQgYE5vcm1hbGl6ZURhdGFgIGFuZCBgU2NhbGVEYXRhYCBhcmUgYmVzdCBmb3IgREdFLiBTZXZlcmFsIHZpZ25ldHRlcyBoYXZlIHBlcmZvcm1lZCBib3RoCgpgc2VsZWN0aW9uLm1ldGhvZAkKSG93IHRvIGNob29zZSB0b3AgdmFyaWFibGUgZmVhdHVyZXMuIENob29zZSBvbmUgb2YgOgoKdnN0OiBGaXJzdCwgZml0cyBhIGxpbmUgdG8gdGhlIHJlbGF0aW9uc2hpcCBvZiBsb2codmFyaWFuY2UpIGFuZCBsb2cobWVhbikgdXNpbmcgbG9jYWwgcG9seW5vbWlhbCByZWdyZXNzaW9uIChsb2VzcykuIFRoZW4gc3RhbmRhcmRpemVzIHRoZSBmZWF0dXJlIHZhbHVlcyB1c2luZyB0aGUgb2JzZXJ2ZWQgbWVhbiBhbmQgZXhwZWN0ZWQgdmFyaWFuY2UgKGdpdmVuIGJ5IHRoZSBmaXR0ZWQgbGluZSkuIEZlYXR1cmUgdmFyaWFuY2UgaXMgdGhlbiBjYWxjdWxhdGVkIG9uIHRoZSBzdGFuZGFyZGl6ZWQgdmFsdWVzIGFmdGVyIGNsaXBwaW5nIHRvIGEgbWF4aW11bSAoc2VlIGNsaXAubWF4IHBhcmFtZXRlcikuCgptZWFuLnZhci5wbG90IChtdnApOiBGaXJzdCwgdXNlcyBhIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSBhdmVyYWdlIGV4cHJlc3Npb24gKG1lYW4uZnVuY3Rpb24pIGFuZCBkaXNwZXJzaW9uIChkaXNwZXJzaW9uLmZ1bmN0aW9uKSBmb3IgZWFjaCBmZWF0dXJlLiBOZXh0LCBkaXZpZGVzIGZlYXR1cmVzIGludG8gbnVtLmJpbiAoZGVhZnVsdCAyMCkgYmlucyBiYXNlZCBvbiB0aGVpciBhdmVyYWdlIGV4cHJlc3Npb24sIGFuZCBjYWxjdWxhdGVzIHotc2NvcmVzIGZvciBkaXNwZXJzaW9uIHdpdGhpbiBlYWNoIGJpbi4gVGhlIHB1cnBvc2Ugb2YgdGhpcyBpcyB0byBpZGVudGlmeSB2YXJpYWJsZSBmZWF0dXJlcyB3aGlsZSBjb250cm9sbGluZyBmb3IgdGhlIHN0cm9uZyByZWxhdGlvbnNoaXAgYmV0d2VlbiB2YXJpYWJpbGl0eSBhbmQgYXZlcmFnZSBleHByZXNzaW9uLgoKZGlzcGVyc2lvbiAoZGlzcCk6IHNlbGVjdHMgdGhlIGdlbmVzIHdpdGggdGhlIGhpZ2hlc3QgZGlzcGVyc2lvbiB2YWx1ZXNgCgoKCgpgYGB7cn0Kc2V1cmF0Lm9iamVjdCA8LSBOb3JtYWxpemVEYXRhKHNldXJhdC5vYmplY3QsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIkxvZ05vcm1hbGl6ZSIsIHNjYWxlLmZhY3RvciA9IDEwMDAwKQpgYGAKCgoKCmBgYHtyfQpzZXVyYXQub2JqZWN0IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKHNldXJhdC5vYmplY3QsIHNlbGVjdGlvbi5tZXRob2QgPSAidnN0IiwgbmZlYXR1cmVzID0gMjAwMCkKYGBgCgpGaW5kIHZhcmlhYmxlIGZlYXR1cmVzCmBgYHtyIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQgPSAyfQpzZXVyYXQub2JqZWN0IDwtIEZpbmRWYXJpYWJsZUZlYXR1cmVzKHNldXJhdC5vYmplY3QsIHNlbGVjdGlvbi5tZXRob2QgPSAidnN0IiwgbmZlYXR1cmVzID0gMjAwMCkKdG9wMTAgPC0gaGVhZChWYXJpYWJsZUZlYXR1cmVzKHNldXJhdC5vYmplY3QpLCAxMCkKcGxvdDEgPC0gVmFyaWFibGVGZWF0dXJlUGxvdChzZXVyYXQub2JqZWN0KQpwbG90MiA8LSBMYWJlbFBvaW50cyhwbG90ID0gcGxvdDEsIHBvaW50cyA9IHRvcDEwLCByZXBlbCA9IFRSVUUpCnBsb3QxICsgcGxvdDIKCmBgYAoKU2NhbGUgZGF0YSAobGluZWFyIHRyYW5zZm9ybWF0aW9uKQoKYGBge3J9CmFsbC5nZW5lcyA8LSByb3duYW1lcyhzZXVyYXQub2JqZWN0KQpzZXVyYXQub2JqZWN0IDwtIFNjYWxlRGF0YShzZXVyYXQub2JqZWN0LCBmZWF0dXJlcyA9IGFsbC5nZW5lcykKYGBgCgoKIyMjIFNhdmUgcHJvZ3Jlc3MKCmBgYHtyfQpzYXZlLmltYWdlKGZpbGUgPSBwYXN0ZTAocHJvamVjdE5hbWUsICcuUkRhdGEnKSkKYGBgCgoKIyMgUENBCgpsaW5lYXIgZGltZW5zaW9uYWwgcmVkdWN0aW9uLiBEZWZhdWx0IGFyZSBiYXNlZCBvbiBWYXJpYWJsZUZlYXR1cmVzLCBidXQgY2FuIGJlIGNoYW5nZWQKCmBgYHtyfQpzZXVyYXQub2JqZWN0IDwtIFJ1blBDQShzZXVyYXQub2JqZWN0LCBmZWF0dXJlcyA9IFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0gc2V1cmF0Lm9iamVjdCkpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9ClZpekRpbUxvYWRpbmdzKHNldXJhdC5vYmplY3QsIGRpbXMgPSAxOjYsIG5mZWF0dXJlcyA9IDEwLCByZWR1Y3Rpb24gPSAicGNhIiwgbmNvbCA9IDIpCmBgYAoKRGltUGxvdCBjb2xvcmVkIGJ5IG9yaWcuaWRlbnQKYGBge3J9CkRpbVBsb3Qoc2V1cmF0Lm9iamVjdCwgcmVkdWN0aW9uID0gInBjYSIsIGdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiKQpgYGAKCgoKTGV0J3MgcHV0IGluIGEgY29uY2VydGVkIGVmZm9ydCB0byBwaWNrIHRoZSByaWdodCBkaW1lbnNpb25hbGl0eSB1c2luZyB0aGUgbmV3ZXN0IHNvZnR3YXJlCgpgYGB7cn0KamFja3N0cmF3LmRpbSA8LSA0MApzZXVyYXQub2JqZWN0IDwtIEphY2tTdHJhdyhzZXVyYXQub2JqZWN0LCBudW0ucmVwbGljYXRlID0gMTAwLCBkaW1zID0gamFja3N0cmF3LmRpbSkgI3J1bnMgfjUwIG1pbgpzZXVyYXQub2JqZWN0IDwtIFNjb3JlSmFja1N0cmF3KHNldXJhdC5vYmplY3QsIGRpbXMgPSAxOmphY2tzdHJhdy5kaW0pCnNhdmUuaW1hZ2UocGFzdGUwKHByb2plY3ROYW1lLCAiLlJEYXRhIikpCmBgYAoKCkRyYXcgZGltLnJlZHVjdGlvbiBwbG90cwoKYGBge3J9CkphY2tTdHJhd1Bsb3Qoc2V1cmF0Lm9iamVjdCwgZGltcyA9IDI1OjM2KQpgYGAKYGBge3IsIGZpZ3VyZXMtc2lkZSwgZmlnLnNob3c9J2hvbGQnLCBvdXQud2lkdGg9IjUwJSJ9CkVsYm93UGxvdChzZXVyYXQub2JqZWN0LCBuZGltcyA9IDUwKQpwZXJjZW50LnZhcmlhbmNlKHNldXJhdC5vYmplY3RAcmVkdWN0aW9ucyRwY2FAc3RkZXYpCmBgYApOdW1iZXIgb2YgUENzIGRlc2NyaWJpbmcgWCUgb2YgdmFyaWFuY2UKCmBgYHtyfQp0b3QudmFyIDwtIHBlcmNlbnQudmFyaWFuY2Uoc2V1cmF0Lm9iamVjdEByZWR1Y3Rpb25zJHBjYUBzdGRldiwgcGxvdC52YXIgPSBGQUxTRSwgcmV0dXJuLnZhbCA9IFRSVUUpCnBhc3RlMCgiTnVtIHBjcyBmb3IgODAlIHZhcmlhbmNlOiIsIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gODApKSkKcGFzdGUwKCJOdW0gcGNzIGZvciA4NSUgdmFyaWFuY2U6IiwgbGVuZ3RoKHdoaWNoKGN1bXN1bSh0b3QudmFyKSA8PSA4NSkpKQpwYXN0ZTAoIk51bSBwY3MgZm9yIDkwJSB2YXJpYW5jZToiLCBsZW5ndGgod2hpY2goY3Vtc3VtKHRvdC52YXIpIDw9IDkwKSkpCnBhc3RlMCgiTnVtIHBjcyBmb3IgOTUlIHZhcmlhbmNlOiIsIGxlbmd0aCh3aGljaChjdW1zdW0odG90LnZhcikgPD0gOTUpKSkKCmBgYAoKIyBLTk4gZGltZW5zaW9uYWwgcmVkdWN0aW9uCiMjIERpbTM2CgozNiBQQ3MgZGVzY3JpYmUgOTUlIHBlcmNlbnQgb2YgdmFyaWFuY2UsIGJ1dCBvbmUgb2YgdGhlIGVhcmxpZXIgY3JpdGlxdWVzIHdhcyB0aGF0IHRvbyBtYW55IGRpbWVuc2lvbnMgd2VyZSBpbmNsdWRlZC4gQmVzdCB3YXkgdG8gZGVhbCB3aXRoIHRoaXMgaXMgcHJvYmFibHkgdG8gdHJ5IGEgY291cGxlIG9mIGRpZmZlcmVudCBvbmVzLiBMZXQncyBzdGFydCB3aXRoIDM2IGJlY2F1c2UgSSBkYXJlIHRvIHJlYmVsLgoKYGBge3J9CnNldXJhdC5vYmplY3QgPC0gRmluZE5laWdoYm9ycyhzZXVyYXQub2JqZWN0LCBkaW1zID0gMTozNikKc2V1cmF0Lm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0Lm9iamVjdCwgcmVzb2x1dGlvbiA9IDAuNSkKYGBgCgoKCgoKCkdlbmVyYXRlIFVNQVAgZGF0YQpgYGB7cn0Kc2V1cmF0Lm9iamVjdCA8LSBSdW5VTUFQKHNldXJhdC5vYmplY3QsIGRpbXMgPSAxOjM2KSAjIHRvb2sgPCAxIG1pbgpgYGAKCgpQbG90IHRoZSByZXN1bHRzCmBgYHtyfQpEaW1QbG90KHNldXJhdC5vYmplY3QsIAoJCQkJcmVkdWN0aW9uID0gInVtYXAiCgkJCQkpICsgZ2d0aXRsZSgibXNBZ2dyIGRpbTM2IHJlczAuNSIpCgpgYGAKCmBgYHtyfQpEaW1QbG90KHNldXJhdC5vYmplY3QsCgkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsIAoJCQkJZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIKCQkJCSkgKyBnZ3RpdGxlKCJtc0FnZ3QgZGltMzYgb3JpZy5pZGVudCIpCmBgYAoKCgpgYGB7cn0Kc2F2ZVJEUyhzZXVyYXQub2JqZWN0LCBmaWxlID0gIm1zQWdncl9kaW0zNi5yZHMiKQpgYGAKCgojIyBEaW0yNAozNiBkaW1lbnNpb25zIGxvb2tzIHZlcnkgInN3b29weSIuIG1pZ2h0IGhhdmUgYXNrZWQgZm9yIHRvbyBtYW55IGRpbWVuc2lvbnMuIFdpbGwgdHJ5IGRpbSA9IDI0LCB3aGljaCBhY2NvdW50cyBmb3IgOTAlIG9mIHZhcmlhbmNlCgpgYGB7cn0KIyBzZXVyYXQub2JqZWN0IDwtIEZpbmROZWlnaGJvcnMoc2V1cmF0Lm9iamVjdCwgZGltcyA9IDE6MjQpCiMgc2V1cmF0Lm9iamVjdCA8LSBGaW5kQ2x1c3RlcnMoc2V1cmF0Lm9iamVjdCwgcmVzb2x1dGlvbiA9IDAuNSkKIyBzZXVyYXQub2JqZWN0IDwtIFJ1blVNQVAoc2V1cmF0Lm9iamVjdCwgZGltcyA9IDE6MjQpCmBgYAoKCgoKUGxvdCB0aGUgcmVzdWx0cwpgYGB7cn0KIyBEaW1QbG90KHNldXJhdC5vYmplY3QsIAojIAkJCQlyZWR1Y3Rpb24gPSAidW1hcCIKIyAJCQkJKSArIGdndGl0bGUoIm1zQWdnciBkaW0yNCByZXMwLjUiKQoKYGBgCgpgYGB7cn0KIyBEaW1QbG90KHNldXJhdC5vYmplY3QsCiMgCQkJCXJlZHVjdGlvbiA9ICJ1bWFwIiwgCiMgCQkJCWdyb3VwLmJ5ID0gIm9yaWcuaWRlbnQiCiMgCQkJCSkgKyBnZ3RpdGxlKCJtc0FnZ3QgZGltMjQgb3JpZy5pZGVudCIpCmBgYAoKCgpgYGB7cn0KIyBzYXZlUkRTKHNldXJhdC5vYmplY3QsIGZpbGUgPSAibXNBZ2dyX2RpbTI0LnJkcyIpCmBgYAoKCiMjIEtOTiBjb25jbHVzaW9uCjI0IHZzIDM2IGRpbWVuc2lvbnMgZG9lc24ndCBzZWVtIHRvIG1ha2UgdGhhdCBtdWNoIGRpZmZlcmVuY2UgUkUgb3ZlcmFsbCByZWxhdGlvbnNoaXBzIGJldHdlZW4gY2VsbHMuIFNob3VsZCBwcm9iYmFseSBjcmVhdGUgdHdvIHNlcGFyYXRlIG9iamVjdHMgYW5kIHRyeSBhIGZldyBwbG90dGluZyBtZXRob2RzLCBidXQgd2lsbCBzdGFydCBieSBmb2N1c2luZyBvbiAzNiBkaW1lbnNpb25zLgoKIyBDbHVzdGVyIGFuYWx5c2lzCgpXaWxsIHByb2NlZWQgd2l0aCBkaW0gPSAzNiBhbmQgZG8gY2x1c3RlcmluZyBhbmFseXNpcyB3aXRoIGEgcmFuZ2Ugb2YgY2x1c3RlcnMuIExhdGVyIGNhbiBkbyB0aGUgdHJlZS1iYXNlZCBvdmVyLWNsdXN0ZXJpbmcgYXNzZXNzbWVudAoKYGBge3J9CnNldXJhdC5vYmplY3QgPC0gcmVhZFJEUygibXNBZ2dyX2RpbTM2LnJkcyIpCmBgYAoKCiMjIEdlbmVyYXRlIGNsdXN0ZXIgcHJvZmlsZXMKUGlja2luZyByYW5nZSBvZiByZXNvbHV0aW9ucwpgYGB7cn0KZm9yKHggaW4gYygwLjUsIDEsIDEuNSwgMiwgMi41KSl7CglzZXVyYXQub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhzZXVyYXQub2JqZWN0LCByZXNvbHV0aW9uID0geCkKfQpzZXVyYXQub2JqZWN0IDwtIFJ1blVNQVAoc2V1cmF0Lm9iamVjdCwgZGltcyA9IDE6MzYpCmBgYAoKUGxvdCB0aGUgY2x1c3RlcmluZyBvdmVyIGRpZmZlcmVudCByZXNvbHV0aW9ucy4gR29pbmcgdG8gbmVlZCBhIG11Y2ggYmV0dGVyIGNvbG9yIHBhbGV0dGUsIG9yIHRyeSBtaXhpbmcgaW4gc29tZSBkaWZmZXJlbnQgc3ltYm9scwoKYGBge3J9CmZvciAobWV0YS5jb2wgaW4gY29sbmFtZXMoc2V1cmF0Lm9iamVjdEBtZXRhLmRhdGEpKXsKCWlmKGdyZXBsKHBhdHRlcm4gPSAoIlJOQV9zbm5fcmVzIiksIHggPSBtZXRhLmNvbCk9PVRSVUUpewoJCW15cGxvdCA8LSBEaW1QbG90KHNldXJhdC5vYmplY3QsIAoJCQkJCQkJCQkJCWdyb3VwLmJ5ID0gbWV0YS5jb2wsCgkJCQkJCQkJCQkJcmVkdWN0aW9uID0gInVtYXAiLCAKCQkJCQkJCQkJCQljb2xzID0gY29sb3JSYW1wczo6cHJpbWFyeS5jb2xvcnMobiA9IGxlbmd0aChsZXZlbHMoc2V1cmF0Lm9iamVjdEBtZXRhLmRhdGFbW21ldGEuY29sXV0pKSkKCQkJCQkJCQkJCQkpICsgCgkJCWdndGl0bGUocGFzdGUwKCJtc0FnZ3IgZGltMzYgcmVzIiwgZ3N1YigiUk5BX3Nubl9yZXMiLCAiIiwgbWV0YS5jb2wpICkpCgkJcGxvdChteXBsb3QpCgl9Cn0KYGBgCgoKCgpgYGB7cn0KIyBzYXZlUkRTKHNldXJhdC5vYmplY3QsIGZpbGUgPSAibXNBZ2dyX2RpbTM2LnJkcyIpCmBgYAoKIyMgRXZhbHVhdGUgY2x1c3RlciBzdGFiaWxpdHkKTXVzdCBlbnN1cmUgd2UgaGF2ZSB0aGUgcmlnaHQgY2x1c3RlciBzdGFiaWxpdHksIHRoYXQgaXMsIGNlbGxzIHRoYXQgc3RhcnQgaW4gdGhlIHNhbWUgY2x1c3RlciB0ZW5kIHRvIHN0YXkgaW4gdGhlIHNhbWUgY2x1c3Rlci4gSWYgeW91ciBkYXRhIGlzIG92ZXItY2x1c3RlcmVkLCBjZWxscyB3aWxsIGJvdW5jZSBiZXR3ZWVuIGdyb3Vwcy4KCkZvbGxvd2luZyBbdGhpcyB0dXRvcmlhbCBieSBNYXR0IE8uXS5odHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vMTAtdGlwcy1mb3ItY2hvb3NpbmctdGhlLW9wdGltYWwtbnVtYmVyLW9mLWNsdXN0ZXJzLTI3N2U5M2Q3MmQ5Mi4KCiMjIyBDbHVzdHJlZQpQcmV2aW91c2x5IG15IGZhdm91cml0ZSBoYXMgYmVlbiBDbHVzdHJlZSwgd2hpY2ggZ2l2ZXMgYSBuaWNlIHZpc3VhbApOQjogRm9yIHNvbWUgcmVhc29uIGBjbHVzdHJlZTo6Y2x1c3RyZWUoKWAgZGlkbid0IHdvcmssIHdoZXJlYXMgYGxpYnJhcnkoY2x1c3RyZWUpYCBmb2xsb3dlZCBieSBgY2x1c3RyZWUoKWAgZGlkLgoKYGBge3IgZmlnLmhlaWdodD01fQpjbHVzdHJlZShzZXVyYXQub2JqZWN0LCBwcmVmaXggPSAiUk5BX3Nubl9yZXMuIiwgbm9kZV9jb2xvdXIgPSAic2MzX3N0YWJpbGl0eSIpICsgCglzY2FsZV9jb2xvcl9jb250aW51b3VzKGxvdyA9ICdyZWQzJywgaGlnaCA9ICd3aGl0ZScpCmBgYAoKCgpgYGB7ciBmaWcuaGVpZ2h0PTV9CmNsdXN0cmVlKHNldXJhdC5vYmplY3QsIHByZWZpeCA9ICJSTkFfc25uX3Jlcy4iLCBleHByZXMgPSAnZGF0YScsIG5vZGVfY29sb3VyID0gInNjM19zdGFiaWxpdHkiKSArIAoJc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAncmVkMycsIGhpZ2ggPSAnd2hpdGUnKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTV9CmNsdXN0cmVlKHNldXJhdC5vYmplY3QsIHByZWZpeCA9ICJSTkFfc25uX3Jlcy4iLCBleHByZXMgPSAnc2NhbGUuZGF0YScsIG5vZGVfY29sb3VyID0gInNjM19zdGFiaWxpdHkiKSArIAoJc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAncmVkMycsIGhpZ2ggPSAnd2hpdGUnKQpgYGAKCgoKYGBge3IgZmlnLmhlaWdodD01fQpjbHVzdHJlZShzZXVyYXQub2JqZWN0LCBwcmVmaXggPSAiUk5BX3Nubl9yZXMuIiwgZXhwcmVzID0gJ2NvdW50cycsIG5vZGVfY29sb3VyID0gInNjM19zdGFiaWxpdHkiKSArIAoJc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAncmVkMycsIGhpZ2ggPSAnd2hpdGUnKQpgYGAKCgoKClRoZXNlIGRhdGEgc3VnZ2VzdCB0aGF0IG5vZGUgc3RhYmlsaXR5IGlzIGF3ZWZ1bCEgTmVlZCB0byBmaWd1cmUgb3V0IGlmIHRoaXMgaXMgYSBkaW1lbnNpb25hbCByZWR1Y3Rpb24gZXJyb3Igb3IgYSBjbHVzdGVyaW5nIGVycm9yLgoKCiMgV2h5IGFyZSBjbHVzdGVycyBzbyB1bnN0YWJsZT8KRGlmZmVyZW5jZXMgY291bGQgaW5jbHVkZToKKiBjZWxscyBpbiBlYWNoIHBvcHVsYXRpb24gKGNlbGxyYW5nZXIgdjYgaW5jbHVkZXMgbW9yZSBjZWxscyB0aGFuIGNlbGxyYW5nZXIgdjEsIGVzcGVjaWFsbHkgaW4gTUVQKQoqIGRpbWVuc2lvbmFsaXR5IGlzIGluY29ycmVjdAoqIFNjYWxlRGF0YSBkaWRudCBhY2NvdW50IGZvciByZWdyZXNzaW9uIGZhY3RvcnMgKGUuZy4sICJuQ291bnRzX1JOQSIgb3IgIm5GZWF0dXJlc19STkEiKQoqIERpZCBub3QgY29uc2lkZXIgY2VsbCBjeWNsZQoqIEluY29ycmVjdCBub3JtYWxpemF0aW9uL3NjYWxpbmcgbWV0aG9kCiogQ2x1c3RlcmluZyBpcyB0b28gc3RyaWN0IG9yIG5vdCBzdHJpY3QgZW5vdWdoCiogbmVpZ2hib3Job29kIGFuYWx5c2lzIHVzZWQgd3JvbmcgcGFyYW1ldGVycwoqIFNob3VsZCBpbmNsdWRlIG1pdG9DIGZpbHRlciAodGhlcmUncyBhIGNodW5rIG9mIE1FUCB3LyBtaXRvQyBAIH40MCUpCiogU0NUcmFuc2Zvcm0gYWNjb3VudHMgYmV0dGVyIGZvciBzb3VyY2VzIG9mIHZhcmlhYmlsaXR5CgpgYGB7cn0KIyBOdW1iZXIgb2YgZmlsdGVyZWQgY2VsbHMgbGVmdCBpbiBlYWNoIHBvcApzYXBwbHkoYygiTFNLbTIiLCAiQ01QbTIiLCAiTUVQbSIsICJHTVBtIiksIGZ1bmN0aW9uKHgpIChjKG5yb3coc2V1cmF0Lm9iamVjdEBtZXRhLmRhdGFbc2V1cmF0Lm9iamVjdEBtZXRhLmRhdGEkb3JpZy5pZGVudCA9PSB4LF0pKSkpCmBgYAoKYGBge3J9CmZvciAoeCBpbiBjKCJMU0ttMiIsICJDTVBtMiIsICJNRVBtIiwgIkdNUG0iKSl7CgloID0gaGlzdChzZXVyYXQub2JqZWN0QG1ldGEuZGF0YVtzZXVyYXQub2JqZWN0QG1ldGEuZGF0YSRvcmlnLmlkZW50ID09IHgsICdwZXJjZW50Lm10J10sIGJyZWFrcyA9IDMwLCBwbG90ID0gRkFMU0UpCgloJGRlbnNpdHkgPSBoJGNvdW50cy9zdW0oaCRjb3VudHMpKjEwMAoJcGxvdChoLGZyZXE9RkFMU0UsIG1haW4gPSAgcGFzdGUoeCwgInBlcmNlbnQgbWl0b0MiKSwgeGxhYiA9ICJwZXJjZW50IG1pdG9DIiwgeWxhYiA9ICJGcmVxdWVuY3kiKQp9CmBgYAoKTG9va3MgbGlrZSBNRVBtIGlzIHRoZSBvbmx5IHNhbXBsZSB3aXRoIHRoYXQgaHVnZSBNaXRvQyAlIGx1bXAgQCA0MCUuIFdoYXQgZG8gdGhlc2UgY2VsbHMgbG9vayBsaWtlLCBvdGhlcndpc2U/CgpgYGB7ciBmaWcud2lkdGg9NX0KVmxuUGxvdChzdWJzZXQoc2V1cmF0Lm9iamVjdCwgc3Vic2V0ID0gb3JpZy5pZGVudCA9PSAiTUVQbSIpLCAKCQkJCWZlYXR1cmVzID0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAicGVyY2VudC5tdCIpLCBuY29sID0gMSwgcHQuc2l6ZSA9IDAsIGZpbGwuYnkgPSAnaWRlbnQnLCBmbGlwID0gVFJVRSkKYGBgClNhdmUgZGltMzYgYXMgaXMgYW5kIHRyeSBjbHVzdGVyaW5nIGFuYWx5c2lzIEAgZGltMjQKYGBge3J9CnNhdmVSRFMoc2V1cmF0Lm9iamVjdCwgZmlsZSA9ICJtc0FnZ3JfQW5hbHlzaXNDb2RlL21zQWdncl9kaW0zNi5yZHMiKQpgYGAKCgoKIyBSZXBlYXQgY2x1c3RlcmluZyB3aXRoIGRpbTI0Ck9uZSBwb3NzaWJpbGl0eSBpcyB0aGF0IEkndmUgaW5jbHVkZWQgdG9vIG1hbnkgZGltZW5zaW9ucy4gV2lsbCBzZWUgaWYgOTAlIGluY3JlYXNlcyBzdGFiaWxpdHkuCgpgYGB7cn0Kc2V1cmF0Lm9iamVjdCA8LSBGaW5kTmVpZ2hib3JzKHNldXJhdC5vYmplY3QsIGRpbXMgPSAxOjI0KQpzZXVyYXQub2JqZWN0IDwtIEZpbmRDbHVzdGVycyhzZXVyYXQub2JqZWN0LCByZXNvbHV0aW9uID0gMC41KQpzZXVyYXQub2JqZWN0IDwtIFJ1blVNQVAoc2V1cmF0Lm9iamVjdCwgZGltcyA9IDE6MjQpCmBgYAoKU2F2ZSBvYmplY3QKYGBge3J9CnNhdmVSRFMoc2V1cmF0Lm9iamVjdCwgZmlsZSA9ICJtc0FnZ3JfZGltMjQucmRzIikKYGBgCgoKCgpgYGB7cn0KZm9yIChtZXRhLmNvbCBpbiBjb2xuYW1lcyhzZXVyYXQub2JqZWN0QG1ldGEuZGF0YSkpewoJaWYoZ3JlcGwocGF0dGVybiA9ICgiUk5BX3Nubl9yZXMiKSwgeCA9IG1ldGEuY29sKT09VFJVRSl7CgkJbXlwbG90IDwtIERpbVBsb3Qoc2V1cmF0Lm9iamVjdCwgCgkJCQkJCQkJCQkJZ3JvdXAuYnkgPSBtZXRhLmNvbCwKCQkJCQkJCQkJCQlyZWR1Y3Rpb24gPSAidW1hcCIsIAoJCQkJCQkJCQkJCWNvbHMgPSBjb2xvclJhbXBzOjpwcmltYXJ5LmNvbG9ycyhuID0gbGVuZ3RoKGxldmVscyhzZXVyYXQub2JqZWN0QG1ldGEuZGF0YVtbbWV0YS5jb2xdXSkpKQoJCQkJCQkJCQkJCSkgKyAKCQkJZ2d0aXRsZShwYXN0ZTAoIm1zQWdnciBkaW0zNiByZXMiLCBnc3ViKCJSTkFfc25uX3JlcyIsICIiLCBtZXRhLmNvbCkgKSkKCQlwbG90KG15cGxvdCkKCX0KfQpgYGAKCgoKCiMjIEV2YWx1YXRlIGNsdXN0ZXIgc3RhYmlsaXR5Ck11c3QgZW5zdXJlIHdlIGhhdmUgdGhlIHJpZ2h0IGNsdXN0ZXIgc3RhYmlsaXR5LCB0aGF0IGlzLCBjZWxscyB0aGF0IHN0YXJ0IGluIHRoZSBzYW1lIGNsdXN0ZXIgdGVuZCB0byBzdGF5IGluIHRoZSBzYW1lIGNsdXN0ZXIuIElmIHlvdXIgZGF0YSBpcyBvdmVyLWNsdXN0ZXJlZCwgY2VsbHMgd2lsbCBib3VuY2UgYmV0d2VlbiBncm91cHMuCgpGb2xsb3dpbmcgW3RoaXMgdHV0b3JpYWwgYnkgTWF0dCBPLl0uaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tLzEwLXRpcHMtZm9yLWNob29zaW5nLXRoZS1vcHRpbWFsLW51bWJlci1vZi1jbHVzdGVycy0yNzdlOTNkNzJkOTIuCgojIyMgQ2x1c3RyZWUKUHJldmlvdXNseSBteSBmYXZvdXJpdGUgaGFzIGJlZW4gQ2x1c3RyZWUsIHdoaWNoIGdpdmVzIGEgbmljZSB2aXN1YWwKTkI6IEZvciBzb21lIHJlYXNvbiBgY2x1c3RyZWU6OmNsdXN0cmVlKClgIGRpZG4ndCB3b3JrLCB3aGVyZWFzIGBsaWJyYXJ5KGNsdXN0cmVlKWAgZm9sbG93ZWQgYnkgYGNsdXN0cmVlKClgIGRpZC4KCmBgYHtyIGZpZy5oZWlnaHQ9NX0KY2x1c3RyZWUoc2V1cmF0Lm9iamVjdCwgcHJlZml4ID0gIlJOQV9zbm5fcmVzLiIsIG5vZGVfY29sb3VyID0gInNjM19zdGFiaWxpdHkiKSArIAoJc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAncmVkMycsIGhpZ2ggPSAnd2hpdGUnKQpgYGAKClRoaW5rIEknbGwgZXhwbG9yZSByZWdyZXNzaW9uIGZhY3RvcnMgdXNpbmcgU0NUcmFuc2Zvcm0gaW4gbmV3IGRvY3VtZW50Lgo=